package ws

import (
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	"go.uber.org/zap"
	"strconv"
	"strings"
	"sync"
	"wzgames/internal/secure"
	"wzgames/pkg/actor/actor"
	"wzgames/pkg/common/pque"
	"wzgames/pkg/logger"
	"wzgames/pkg/utils/byteutil"
	"wzgames/pkg/utils/dateutil"
	"wzgames/pkg/utils/netutil"
)

type wsSession struct {
	id       int64
	acceptor IWebSocketAcceptor
	exitSync sync.WaitGroup

	conn     *websocket.Conn
	hPid     *actor.PID
	handle   int64
	token    string
	remoteIP string

	sendQueue *pque.Pipe

	ok bool

	endNotify func()
}

func newSession(conn *websocket.Conn, p IWebSocketAcceptor, clientIP string, endNotify func()) *wsSession {
	ws := &wsSession{
		conn:      conn,
		endNotify: endNotify,
		acceptor:  p,
		remoteIP:  clientIP,
		sendQueue: pque.NewPipe(),
	}
	if ws.remoteIP == "" {
		ws.remoteIP, _ = GetRemoteAddrss(ws)
	}

	return ws
}

func (s *wsSession) ID() int64 {
	return s.id
}

func (s *wsSession) SetID(id int64) {
	s.id = id
}

func (s *wsSession) Raw() interface{} {
	if s.conn == nil {
		return nil
	}

	return s.conn
}

func (s *wsSession) SetLink(pid *actor.PID, handle int64) {
	s.hPid = pid
	s.handle = handle
}

func (s *wsSession) LinkPid() *actor.PID {
	return s.hPid
}

func (s *wsSession) LinkID() int64 {
	return s.handle
}

func (s *wsSession) RemoteIP() string {
	return s.remoteIP
}

func (s *wsSession) Close() {
	s.ok = false
	s.sendQueue.Add(nil)
}

func (s *wsSession) Acceptor() IWebSocketAcceptor {
	return s.acceptor
}

func (s *wsSession) Send(msg interface{}) {
	if !s.ok {
		return
	}
	s.sendQueue.Add(msg)
}

func (s *wsSession) sendLoop() {
	var writeList []interface{}
	for {
		writeList = writeList[0:0]
		exit := s.sendQueue.Pick(&writeList)

		for _, msg := range writeList {
			s.acceptor.Server().SendMessage(s, msg)
		}

		if exit {
			break
		}
	}

	if s.conn != nil {
		s.conn.Close()
		s.conn = nil
	}

	s.exitSync.Done()
}

func (s *wsSession) protectedReadMessage() (msg interface{}, err error) {
	defer func() {
		if e := recover(); e != nil {
			logger.Error("[WS] IO read panic", zap.Any("Error", e))
			s.Close()
		}
	}()

	msg, err = s.acceptor.Server().ReadMessage(s)

	return
}

func (s *wsSession) recvLoop() {
	capturePanic := s.acceptor.Server().CaptureIOPanic()

	for s.conn != nil {
		var msg interface{}
		var err error

		if capturePanic {
			msg, err = s.protectedReadMessage()
		} else {
			msg, err = s.acceptor.Server().ReadMessage(s)
		}

		if err != nil {
			s.ok = false
			if !netutil.IsEOFOrNetReadError(err) {
				//logger.Error("[WS] session closed", zap.Error(err))
			}
			s.acceptor.Server().ProcData(s, SessionClosed{C: -1})
			break
		}

		s.acceptor.Server().ProcData(s, msg)
	}

	s.Close()
	s.exitSync.Done()
}

func (s *wsSession) Token() string {
	if s.token == "" {
		_uuid, e := uuid.NewRandom()
		if e != nil {
			s.token = secure.Md5Hex(byteutil.StringToSliceByte(strconv.FormatInt(dateutil.UnixNano(), 10)))
		} else {
			s.token = strings.ReplaceAll(uuid.Must(_uuid, nil).String(), "-", "")
		}
	}

	return s.token
}

func (s *wsSession) Ok() bool {
	return s.ok
}

func (s *wsSession) Start() {
	s.acceptor.Server().AddSession(s)

	s.exitSync.Add(2)

	go func() {
		s.exitSync.Wait()
		s.acceptor.Server().RemoveSession(s)
		if s.endNotify != nil {
			s.endNotify()
		}
	}()

	go s.recvLoop()

	go s.sendLoop()

	s.acceptor.Server().ProcData(s, SessionAccepted{})
	s.ok = true
}
